package triIndex;

import java.util.Vector;

import transaction.*;
import triStorage.*;
import triBasic.*;
import triInstance.*;
import triFileSystem.*;
import utils.*;

public class NodeIndex implements TriModule {

	//File that stores NodeSnapshot
	private TriMMPFile mmpfile;
	
	private IWriter writer;
	private Inode inode;
	
	//Index File
	private TriMMPFile metaMMPfile;

	private RWLock lock;
	
	private int record_per_page;
	
	private static final int RECORD_SIZE = Constants.INT_SIZE + Constants.LONG_SIZE * 2;
	private static final int NODESNAPSHOT_OFFSET = Constants.INT_SIZE;
	private static final int INODE_OFFSET = Constants.INT_SIZE + Constants.LONG_SIZE;
	
	public NodeIndex(String node_idx_filename, int node_idx_pagebuffer_size, int node_idx_pagesize, 
			String node_idx_meta_filename, int node_idx_meta_pagebuffer_size,
			int node_idx_meta_pagesize) {
		
		mmpfile = new TriMMPFile(node_idx_filename, node_idx_pagebuffer_size, node_idx_pagesize);
		metaMMPfile = new TriMMPFile(node_idx_meta_filename, node_idx_meta_pagebuffer_size,
				node_idx_meta_pagesize);
		
		record_per_page = node_idx_meta_pagesize / RECORD_SIZE;
		
		lock = new RWLock();
	}
	
	@Override
	public void init(Transaction tx) {
		mmpfile.open();
		
		inode = new FileDir(mmpfile.getmaxPageId());
		writer = new WPageArray(inode, mmpfile, tx);
		metaMMPfile.open();
	}
	
	public void createNewNode(NodeSnapshot snapshot, INode node, Transaction tx) {
		long pos1 = insertNodeSnapshot(snapshot, tx);
		long pos2 = insertINode(node, tx);
		insertOffset(snapshot.getNodeId(), pos1, pos2, tx);
	}
	
	public NodeSnapshot getNodeSnapshot(int nodeId, Transaction tx) {
		NodeSnapshot snapshot = new NodeSnapshot();
		
		lock.lockRead(tx);
		try {
			long pos = getNodeSnapshotOffset(nodeId);
			IReader reader = new RPageArray(inode, mmpfile);
			reader.seek(pos);
			snapshot.load(reader);

			reader.dispose();
			return snapshot;
		}
		finally {
			lock.unlockRead(tx);
		}
	}
	
	public TriPath getTriPath(int nodeId, Transaction tx) {
		lock.lockRead(tx);
		try {
			long pos = getINodeOffset(nodeId);
			IReader reader = new RPageArray(inode, mmpfile);
			reader.seek(pos);
			TriPath path = loadTriPath(reader);
			
			reader.dispose();
			return path;
		}
		finally {
			lock.unlockRead(tx);
		}
	}
	
	private long insertNodeSnapshot(NodeSnapshot snapshot, Transaction tx) {
		lock.lockWrite(tx);
		try {
			long pos = writer.getWritePos();
			snapshot.flush(writer, tx);
			System.out.println("insert " + snapshot.getTagName() + " at " + pos);
			writer.dispose();
			return pos;
		}
		finally {
			lock.lockWrite(tx);
		}
	}
	
	private long insertINode(INode node, Transaction tx) {
		lock.lockWrite(tx);
		try {
			long pos = writer.getWritePos();
			this.flushTriPath(node.getTriPath(), writer, tx);
			writer.dispose();
			return pos;
		}
		finally {
			lock.unlockWrite(tx);
		}
	}
	
	private TriPath loadTriPath(IReader reader) {
		int len = reader.readInt();
		Vector<NodeInfo> info = new Vector<NodeInfo> (len);
		for (int i = 0; i < len; ++i)
			info.add(new NodeInfo(reader.readInt(), reader.readString()));
		
		return new TriPath(info);
	}
	
	private void flushTriPath(TriPath path, IWriter writer, Transaction tx) {
		writer.appendInt(path.size(), tx);
		for (int i = 0; i < path.size(); ++i) {
			writer.appendInt(path.get(i).getNodeId(), tx);
			writer.appendString(path.get(i).getTagName(), tx);
		}
	}
	
	private void insertOffset(int nodeId, long pos1, long pos2, Transaction tx) {
		int pageId = nodeId / record_per_page ;
		int offset = nodeId % record_per_page * NodeIndex.RECORD_SIZE;
		
		alloc(pageId);
		metaMMPfile.writeInt(pageId, offset, nodeId, tx);
		metaMMPfile.writeLong(pageId, offset + NODESNAPSHOT_OFFSET, pos1, tx);
		metaMMPfile.writeLong(pageId, offset + INODE_OFFSET, pos2, tx);
	}
	
	private long getINodeOffset(int nodeId) {
		int pageId = nodeId / record_per_page ;
		int offset = nodeId % record_per_page * NodeIndex.RECORD_SIZE;
		
		return metaMMPfile.readLong(pageId, offset + INODE_OFFSET);
	}
	
	private long getNodeSnapshotOffset(int nodeId) {
		int pageId = nodeId / record_per_page ;
		int offset = nodeId % record_per_page * NodeIndex.RECORD_SIZE;
		
		return metaMMPfile.readLong(pageId, offset + NODESNAPSHOT_OFFSET);
	}
	
	private void alloc(int pageId) {
		while (metaMMPfile.getmaxPageId() < pageId)
			metaMMPfile.allocNewPage();
	}
	
	@Override
	public void dispose(Transaction tx) {
		writer.dispose();
		mmpfile.close();
		metaMMPfile.close();
	}
	
	
}
